#include "ngx_ngim_http_module.h"
#include "ngx_ngim_curl.h"

#define  ngx_ngim_prepare_response_has_done(s,proto)  ngx_ngim_request_has_done((s),(proto),NGX_NGIM_HTTP_CTX_PREPARE_RESPONSE_FINISH)

static ngx_conf_bitmask_t      ngx_ngim_task_http_which_mask[] = {
    { ngx_string("all"),         NGX_NGIM_WHICH_ALL      },
    { ngx_string("rnd"),         NGX_NGIM_WHICH_RND      },
    { ngx_string("idx"),         NGX_NGIM_WHICH_IDX      },
    { ngx_null_string,                  0                     }
};

static ngx_conf_bitmask_t      ngx_ngim_task_http_async_mask[] = {
    { ngx_string("sync"),         NGX_NGIM_ASYNC_SYNC     },
    { ngx_string("asyn"),         NGX_NGIM_ASYNC_ASYN     },
    { ngx_null_string,                  0                      }
};


ngx_str_t  param_which           = ngx_string("which");
ngx_str_t  param_which_by_idx    = ngx_string("which_by_idx");
ngx_str_t  param_async           = ngx_string("async");

ngx_str_t  json_param_ngim_data  = ngx_string("ngim_data");
ngx_str_t  json_param_ngim_cb    = ngx_string("ngim_cb");



static char      *ngx_conf_set_str_arrays(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t  ngx_ngim_http_postconfiguration(ngx_conf_t *cf);
static void      *ngx_ngim_create_http_loc_conf(ngx_conf_t *cf);
static char      *ngx_ngim_merge_http_loc_conf(ngx_conf_t *cf, void *parent, void *child);


static ngx_int_t  ngx_ngim_http_stage_read_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
static ngx_int_t  ngx_ngim_http_stage_prepare_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
static ngx_int_t  ngx_ngim_http_stage_do_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
static ngx_int_t  ngx_ngim_http_stage_prepare_response_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
static ngx_int_t  ngx_ngim_http_stage_do_response_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
static ngx_int_t  ngx_ngim_http_stage_close_connection_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);
//static ngx_int_t  ngx_ngim_http_stage_async_close_connection_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data);

static ngx_int_t  ngx_ngim_http_request_has_done_handler(ngx_ngim_session_t *s,ngx_int_t proto,ngx_uint_t ctx_stage);
static ngx_int_t  ngx_ngim_http_request_is_async_handler(ngx_ngim_session_t *s,ngx_int_t proto);


static ngx_ngim_request_has_done_pt   next_request_has_done; 

static ngx_ngim_request_is_async_pt   next_request_is_async;

static ngx_command_t  ngx_ngim_http_commands[] = {

    { ngx_string("http"),
      NGX_NGIM_MAIN_CONF|NGX_NGIM_SRV_CONF|NGX_NGIM_TASK_CONF|
                         NGX_CONF_TAKE1,
      ngx_conf_set_flag_slot,
      NGX_NGIM_TASK_CONF_OFFSET,
      offsetof(ngx_ngim_http_loc_conf_t, http),
      NULL},

    { ngx_string("http_which"),
      NGX_NGIM_MAIN_CONF|NGX_NGIM_SRV_CONF|NGX_NGIM_TASK_CONF|
                         NGX_CONF_TAKE1,
      ngx_conf_set_bitmask_slot,
      NGX_NGIM_TASK_CONF_OFFSET,
      offsetof(ngx_ngim_http_loc_conf_t, which),
      ngx_ngim_task_http_which_mask },

    { ngx_string("http_async"),
      NGX_NGIM_MAIN_CONF|NGX_NGIM_SRV_CONF|NGX_NGIM_TASK_CONF|
                         NGX_CONF_TAKE1,
      ngx_conf_set_bitmask_slot,
      NGX_NGIM_TASK_CONF_OFFSET,
      offsetof(ngx_ngim_http_loc_conf_t, async),
      ngx_ngim_task_http_async_mask },

    { ngx_string("urls"),
      NGX_NGIM_MAIN_CONF|NGX_NGIM_SRV_CONF|NGX_NGIM_TASK_CONF|
                         NGX_CONF_1MORE,
      ngx_conf_set_str_arrays,
      NGX_NGIM_TASK_CONF_OFFSET,
      offsetof(ngx_ngim_http_loc_conf_t, urls),
      NULL},

      ngx_null_command
};


static ngx_ngim_module_t  ngx_ngim_http_module_ctx = {
    NULL,                                      /* preconfiguration */
    ngx_ngim_http_postconfiguration,           /* postconfiguration */
    NULL,                                      /* create main configuration */
    NULL,                                      /* init main configuration */
    NULL,                                      /* create server configuration */
    NULL,                                      /* merge server configuration */
    ngx_ngim_create_http_loc_conf,            /* create task configuration */
    ngx_ngim_merge_http_loc_conf              /* merge task configuration */
};


ngx_module_t  ngx_ngim_http_module = {
    NGX_MODULE_V1,
    &ngx_ngim_http_module_ctx,            /* module context */
    ngx_ngim_http_commands,               /* module directives */
    NGX_NGIM_MODULE,                        /* module type */
    NULL,                                   /* init master */
    NULL,                                   /* init module */
    NULL,                                   /* init process */
    NULL,                                   /* init thread */
    NULL,                                   /* exit thread */
    NULL,                                   /* exit process */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_int_t  
ngx_ngim_http_stage_read_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_connection_t    *c      =  s->connection;
   ngx_event_t         *rev    =  c->read;
   u_char              *method =  data; 
   
   ngx_log_debug0(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
           	  "ngx_ngim_http_stage_read_request_handler");
 
 
   if(method == NULL)
      return NGX_ERROR;

   if(ngx_strchr(PROTO_TAG,method[0]) == NULL){
      return NGX_OK; //go on
   }
  
   s->protocol  = NGX_NGIM_PROTO_HTTP;
   rev->handler = ngx_ngim_http_init_request;
   if(rev->ready){
      rev->handler(rev);
   }
   return NGX_DONE;//stop
}


static ngx_int_t  
ngx_ngim_http_stage_prepare_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_connection_t           *c;
   ngx_http_request_t         *r;
   ngx_ngim_http_ctx_t        *ctx;
   ngx_array_t                *tasks;
   ngx_uint_t                  i;
   ngx_ngim_core_srv_conf_t   *cscf; 
   ngx_ngim_core_task_conf_t  **task_conf;
   ngx_ngim_http_loc_conf_t   *hlcf;
   ngx_str_t                  *args,val,*url,*u,*uri;
   ngx_int_t                   v;
   
   ngx_log_debug0(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
           	  "ngx_ngim_http_stage_prepare_request_handler");
  
   c     = s->connection;
   cscf  = ngx_ngim_get_module_srv_conf(s,ngx_ngim_core_module);
   tasks = &cscf->tasks;
   uri   = &s->name;

   if (s->task_conf == NULL) {
      task_conf = tasks->elts;
      for(i=0; i<tasks->nelts; i++){
          ngx_log_debug(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
                    "uri:%V ctx_name:%V %p",
                     uri,&task_conf[i]->name ,task_conf[i]);

          if( (uri->len == task_conf[i]->name.len) && 
               ngx_strncmp(uri->data,task_conf[i]->name.data,task_conf[i]->name.len) == 0
            ){
             s->task_conf = task_conf[i]->task_conf;
          }
      }
   }
 
   if (s->task_conf == NULL) {
       ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                     "cant find ctx name for uri:%V",uri);

       return NGX_ERROR;
   } 

   hlcf         = ngx_ngim_get_module_task_conf(s,ngx_ngim_http_module);
   if(!hlcf->http){
       return NGX_OK;//next
   }

   ctx  = ngx_pcalloc(c->pool,sizeof(ngx_ngim_http_ctx_t));
   if(ctx == NULL){
      return NGX_ERROR;
   }
   ngx_ngim_set_ctx(s,ctx,ngx_ngim_http_module);

   ctx->which     = hlcf->which;
   ctx->which_one = -1;
   ctx->async     = hlcf->async;
   ctx->request   = s->request;
   ctx->session   = s;
   ctx->stage     = NGX_NGIM_HTTP_CTX_INIT;

   r     =  s->request;
   args  = &r->args;
 
   ngx_log_debug(NGX_LOG_DEBUG_NGIM, s->connection->log, 0,
                 "init ctx:%p",ctx);

   if(args->len > 0){
      if(ngx_http_arg(r,param_which.data,param_which.len,&val) == NGX_OK){
         v = ngx_atoi(val.data,val.len); 
         if(v != NGX_ERROR)
            ctx->which = v;
      }
  
      if(ngx_http_arg(r,param_which_by_idx.data,param_which.len,&val) == NGX_OK){
         v = ngx_atoi(val.data,val.len); 
         if(v != NGX_ERROR){
            ctx->which_one = v;
            ctx->which     = NGX_NGIM_WHICH_IDX;
         }
      }
    
      if(ngx_http_arg(r,param_async.data,param_which.len,&val) == NGX_OK){
         v = ngx_atoi(val.data,val.len); 
         if(v != NGX_ERROR)
            ctx->async = v;
      }
   }

  if (ngx_array_init(&ctx->urls, c->pool, 2,
                       sizeof(ngx_str_t)) != NGX_OK  ||
      ngx_array_init(&ctx->request_easyinfo, c->pool, 2,
                       sizeof(void*))     != NGX_OK
    )
  {
    ngx_log_error(NGX_LOG_ERR,c->log,0,
                      "init ctx->urls or ctx->request_easyinfo failed");

    return NGX_ERROR;
  }

  url = hlcf->urls.elts;
  for(i=0; i<hlcf->urls.nelts; i++){

     u = ngx_array_push(&ctx->urls); 
     if(u == NULL){
        ngx_log_error(NGX_LOG_ERR,c->log,0,
                      "push ctx->urls failed");
        return NGX_ERROR;
     }

     if(args->len > 0){
        ngx_uint_t len = url[i].len + args->len + 2; // & and NULL
        u_char *tmp = ngx_pcalloc(c->pool,len);
        if(tmp == NULL) return NGX_ERROR;
        if(ngx_strlchr(url[i].data,url[i].data+url[i].len,'?') == NULL){
           ngx_snprintf(tmp,len,"%V?%V",&url[i],args);
        }else{
           ngx_snprintf(tmp,len,"%V&%V",&url[i],args);
        }
        u->data = tmp;
        u->len  = len;
     }else{
        *u = url[i];
     }

     ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                   "push url:%V",u);
  }

  if(ctx->which == NGX_NGIM_WHICH_RND){
     ctx->which_one = (ngx_uint_t) (((uint64_t) ngx_random() * ctx->urls.nelts) / 0x80000000); 
  }

  return NGX_OK;//next 
}


static ngx_int_t  
ngx_ngim_http_stage_do_request_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_connection_t      *c      =  s->connection;
   ngx_ngim_http_ctx_t   *ctx;
   ngx_int_t              rc;
   ngx_str_t             *url;
   ngx_uint_t             i;
 
   ctx = ngx_ngim_get_module_ctx(s,ngx_ngim_http_module);
   if(ctx == NULL){
      return NGX_OK;//next
   }

   if(s->stage != NGX_NGIM_SESSION_DO_REQUEST){
      ngx_log_error(NGX_LOG_ERR,c->log,0,
                    "ngx_ngim_http_stage_do_request_handler invalid stage expect:%d but:%d",
                     NGX_NGIM_SESSION_DO_REQUEST,ctx->stage);
      return NGX_ERROR;
   }

   url = ctx->urls.elts;

   ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                 "send http request ctx which:%ui async:%ui nurls:%ui",
                  ctx->which,ctx->async,ctx->urls.nelts);

   if(ctx->which == NGX_NGIM_WHICH_ALL){
      for(i=0; i<ctx->urls.nelts; i++){
         if((rc=ngx_ngim_curl_add_url(s,url[i].data,i)) != NGX_OK){
             break;
         }
      }
   }else{
      rc = ngx_ngim_curl_add_url(s,url[ctx->which_one].data,ctx->which_one);
   }

   if(rc != NGX_OK){
      ngx_ngim_curl_cleanup_easyinfo(s,&ctx->request_easyinfo); 
      return NGX_DECLINED;//cancel myself
   }

   if(ctx->async == NGX_NGIM_ASYNC_ASYN){
      ctx->http  = cJSON_CreateString("async");
      if(ctx->http == NULL)
         return NGX_ERROR;
      ctx->stage = NGX_NGIM_HTTP_CTX_DO_REQUEST_FINISH;
   }

   return NGX_OK;//next
}

static ngx_int_t  
ngx_ngim_http_stage_prepare_response_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_connection_t      *c      =  s->connection;
   ngx_ngim_http_ctx_t   *ctx;
 
   ctx = ngx_ngim_get_module_ctx(s,ngx_ngim_http_module);

   if(ctx == NULL || ctx->http == NULL || 
      ctx->stage == NGX_NGIM_HTTP_CTX_PREPARE_RESPONSE_FINISH){
      return NGX_OK;//next
   }

   if(s->root == NULL){
      s->root = cJSON_CreateObject();
   }

   ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                 "ngx_ngim_http_stage_prepare_response_handler done");

   cJSON_AddItemToObject(s->root, "http", ctx->http);
   ctx->stage = NGX_NGIM_HTTP_CTX_PREPARE_RESPONSE_FINISH;

   return NGX_OK;//next
}

static ngx_int_t  
ngx_ngim_http_stage_do_response_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_http_request_t         *r = s->request;
   ngx_connection_t           *c = s->connection;
   ngx_int_t                   rc;
   size_t                      len;
   ngx_chain_t                 out;
   ngx_buf_t                  *b;

   if(s->protocol != NGX_NGIM_PROTO_HTTP){
      return NGX_OK;//next 
   }

   rc = ngx_ngim_prepare_response_has_done(s,-1);
   if(rc != NGX_OK){
     ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                   "request has not done");

     return NGX_DONE;//stop
   } 

   if(s->root == NULL){
      ngx_log_error(NGX_LOG_ERR,c->log,0,
                    "invalid s->root is NULL");
      return NGX_ERROR;//error
   }

   //out=cJSON_Print(root);  cJSON_Delete(root);     printf("%s\n",out);     free(out);
   s->buff  = (u_char*)cJSON_Print(s->root);
   if(s->buff == NULL){
      return NGX_ERROR;//error
   }
   len   = ngx_strlen(s->buff);
  
   ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                 "root cjson:%*s",5,s->buff);

   r->headers_out.status = NGX_HTTP_OK;
   r->headers_out.content_length_n = len;

   /*if (ngx_http_set_content_type(r) != NGX_OK) {
     return NGX_HTTP_INTERNAL_SERVER_ERROR;
   }*/

   rc=ngx_ngim_http_send_header(r);

   ngx_log_debug(NGX_LOG_DEBUG_NGIM,c->log,0,
                 "http send rc:%i",rc);

   //if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) 
   if (rc == NGX_ERROR || rc > NGX_OK) {
       return rc;
   }

   b = ngx_pcalloc(c->pool,sizeof(ngx_buf_t));
   if(b == NULL){
     return NGX_ERROR;
   }

   b->memory   = 1;
   b->last_buf = 1;
   b->flush    = 1;
   //b->recycled = 0;
   b->last_in_chain = 1;

   b->pos  = s->buff;
   b->last = s->buff + len;

   out.buf = b;
   out.next = NULL;
          
   rc = ngx_ngim_write_filter(s,&out);
   if(rc == NGX_AGAIN){
      ngx_ngim_set_write_handler(s,0);
      return NGX_DONE;//stop 
   }

   if(rc != NGX_OK){
      ngx_log_error(NGX_LOG_ERR,c->log,0,
                    "http output filter failed rc:%i",rc);
      return NGX_ERROR;
   }


   ngx_log_debug(NGX_LOG_DEBUG_HTTP, c->log, 0,
                  "ngim writer done: \"%V\"", &s->name);
    
   c->write->handler = ngx_ngim_empty_handler;
    
   ngx_ngim_close_session(s);

   return NGX_DONE;//stop
}


static ngx_int_t  
ngx_ngim_http_stage_close_connection_handler(ngx_ngim_session_t *s,ngx_chain_t *in,void *data)
{
   ngx_connection_t      *c;
   ngx_ngim_http_ctx_t   *ctx;

   c   =  s->connection;
 
   ctx = ngx_ngim_get_module_ctx(s,ngx_ngim_http_module);
   if(ctx == NULL){
      return NGX_OK;//next
   }

   ngx_ngim_curl_cleanup_easyinfo(s,&ctx->request_easyinfo);
 
   return NGX_OK;//next
}


static ngx_int_t  
ngx_ngim_http_request_has_done_handler(ngx_ngim_session_t *s,ngx_int_t proto,ngx_uint_t ctx_stage)
{
   ngx_ngim_http_ctx_t       *ctx;

   ctx = ngx_ngim_get_module_ctx(s,ngx_ngim_http_module);

   if(proto == NGX_NGIM_PROTO_HTTP){
      if(ctx == NULL || ctx->stage != ctx_stage){
         return NGX_AGAIN; 
      }
 
      return NGX_OK; 
   }else if(proto == -1){
      if(ctx != NULL && ctx->stage != ctx_stage){
         return NGX_AGAIN;
      }

      if(next_request_has_done) 
        return next_request_has_done(s,proto,ctx_stage); 
      else
        return NGX_OK;
   }else{
     if(next_request_has_done) 
        return next_request_has_done(s,proto,ctx_stage); 
     else
        return NGX_AGAIN;
   }
}

static ngx_int_t  
ngx_ngim_http_request_is_async_handler(ngx_ngim_session_t *s,ngx_int_t proto)
{
   ngx_ngim_http_ctx_t       *ctx;

   ctx = ngx_ngim_get_module_ctx(s,ngx_ngim_http_module);

   if(proto == NGX_NGIM_PROTO_HTTP){
      if(ctx  && ctx->async == NGX_NGIM_ASYNC_ASYN){
         return NGX_OK; 
      }
 
      return NGX_AGAIN; 
   }else if(proto == -1){
      if(ctx  && ctx->async == NGX_NGIM_ASYNC_ASYN){
         if(next_request_is_async) 
           return next_request_is_async(s,proto); 
         else
           return NGX_OK;
      }
      return NGX_AGAIN; 
   }else{
     if(next_request_is_async) 
        return next_request_is_async(s,proto); 
     else
        return NGX_AGAIN;
   }
}

 

static void *
ngx_ngim_create_http_loc_conf(ngx_conf_t *cf)
{
    ngx_ngim_http_loc_conf_t      *nnhlc;

    nnhlc = ngx_pcalloc(cf->pool, sizeof(ngx_ngim_http_loc_conf_t));

    if (nnhlc == NULL) {
        return NULL;
    }

    nnhlc->http = NGX_CONF_UNSET;
    nnhlc->which = 0;
    nnhlc->async = 0;


    if (ngx_array_init(&nnhlc->urls, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {
        return NULL;
    }

    return nnhlc;
}


static char *
ngx_ngim_merge_http_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_ngim_core_task_conf_t    *ctcf;
    ngx_ngim_http_loc_conf_t     *prev = parent;
    ngx_ngim_http_loc_conf_t     *conf = child;
    ngx_str_t                    *elt;
    ngx_uint_t                    i;

    ctcf = ngx_ngim_conf_get_module_task_conf(cf, ngx_ngim_core_module);

    ngx_conf_merge_value(conf->http,prev->http,0);
    ngx_conf_merge_bitmask_value(conf->which,prev->which,0);
    ngx_conf_merge_bitmask_value(conf->async,prev->async,0);

    if(conf->urls.nelts == 0 && prev->urls.nelts > 0){

       elt  = prev->urls.elts;
       for(i=0; i<prev->urls.nelts; i++){
          ngx_str_t  *turl = ngx_array_push(&conf->urls);
          *turl  = elt[i]; 
       } 

    }

    if(conf->which == 0){
       conf->which = ctcf->which; 
    }

    if(conf->async == 0){
       conf->async = ctcf->async; 
    }

    return NGX_CONF_OK;
}

static ngx_int_t  ngx_ngim_http_postconfiguration(ngx_conf_t *cf)
{
   ngx_ngim_core_main_conf_t          *cmcf;
   ngx_ngim_stage_handler_pt          *h; 
        
   cmcf = ngx_ngim_conf_get_module_main_conf(cf, ngx_ngim_core_module);

   /* register raw event handlers */
   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_READ_REQUEST]);
   *h = ngx_ngim_http_stage_read_request_handler;  

   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_PREPARE_REQUEST]);
   *h = ngx_ngim_http_stage_prepare_request_handler;

   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_DO_REQUEST]);
   *h = ngx_ngim_http_stage_do_request_handler;

   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_PREPARE_RESPONSE]);
   *h = ngx_ngim_http_stage_prepare_response_handler;

   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_DO_RESPONSE]);
   *h = ngx_ngim_http_stage_do_response_handler;

   h = ngx_array_push(&cmcf->events[NGX_NGIM_SESSION_CLOSE_CONNECTION]);
   *h = ngx_ngim_http_stage_close_connection_handler;

   next_request_has_done  = ngx_ngim_request_has_done;
   ngx_ngim_request_has_done = ngx_ngim_http_request_has_done_handler; 

   next_request_is_async  = ngx_ngim_request_is_async;
   ngx_ngim_request_is_async = ngx_ngim_http_request_is_async_handler; 


   return NGX_OK;
}

static char *ngx_conf_set_str_arrays(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char              *p  = (char*)conf;
    ngx_str_t         *value, *s;
    ngx_uint_t         i;
    ngx_array_t       *a;

    a = (ngx_array_t *) (p + cmd->offset);

    value = cf->args->elts;
    for(i=1; i<cf->args->nelts; i++){
       s = ngx_array_push(a);
       if (s == NULL) {
          return NGX_CONF_ERROR;
       }
       *s = value[i];
    }

    return NGX_CONF_OK;
}
